package com.lamatek.tags.google;

import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
import java.net.URLConnection;
import java.util.Date;
import java.util.Vector;

import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.Tag;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.lamatek.tags.google.beans.TrafficEventBean;

/**
 * GoogleMapTrafficTag
 * 
 * This class represents a &lt;googlemaps:traffic> tag. Developers should not subclass nor
 * override it's methods.
 * 
 * @author Tom Cole
 * @version 0.75
 */
public class GoogleMapTrafficTag extends BodyTagSupport implements Serializable {
    
    String id = null;
    String point = null;
    double radius = 0.00d;
    Vector events = null;
    int severity = 1;
    String type = "both"; //incident | construction | both
    GoogleMapIconTag icon = null;
    
    /**
     * This method geocodes all the traffic events within the specified radius of the
     * longitude and latitude provided, and populates them as TrafficEventBeans. These
     * TrafficEventBeans can be retrieved using the getEvent() method.
     * 
     * @param longitude The longitude of the centerpoint from which the radius is derived.
     * @param latitude The latitude of the centerpoint from which the radius is derived.
     */
    public boolean geocode(double longitude, double latitude) {
        boolean coded = true;
        String url = "http://api.local.yahoo.com/MapsService/V1/trafficData?appid=google-jsp-taglib";
        url += "&longitude=" + longitude + "&latitude=" + latitude;
        url += "&severity=" + severity + "&radius=" + radius;
        url += "&include_map=0";
        try {
	        URLConnection con = new URL(url).openConnection();
	        if (con != null) {
	            InputStream input = con.getInputStream();
	            DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
	            Document elements = parser.parse(input);
	            NodeList results = elements.getElementsByTagName("Result");
	            for (int i = 0; i < results.getLength(); i++) {
	                Node result = results.item(i);
	                NamedNodeMap attributes = result.getAttributes();
	                String eventType = attributes.getNamedItem("type").getNodeValue();
	                if (type.equals("both") || type.equals(eventType)) {
	                    if (events == null)
	                        events = new Vector();
	                    TrafficEventBean event = new TrafficEventBean();
	                    event.setType(eventType);
		                NodeList children = result.getChildNodes();
		                for (int j = 0; j < children.getLength(); j++) {
		                    Node child = children.item(j);
		                    Node text = null;
		                    NodeList sub = child.getChildNodes();
		                    for (int c = 0; c < sub.getLength(); c++) {
		                        if (sub.item(c).getNodeName().equals("#text")) {
		                            text = sub.item(c);
		                        }
		                    }
		                    if (child.getNodeName().equals("Title")) {
		                        if (text != null)
		                          event.setTitle(text.getNodeValue());
		                    }
		                    else if (child.getNodeName().equals("Description")) {
		                        if (text != null)
			                          event.setDescription(text.getNodeValue());
		                    }
		                    else if (child.getNodeName().equals("Longitude")) {
		                        if (text != null)
			                          event.setLongitude(Double.parseDouble(text.getNodeValue()));
		                    }
		                    else if (child.getNodeName().equals("Latitude")) {
		                        if (text != null)
			                          event.setLatitude(Double.parseDouble(text.getNodeValue()));
		                    }
		                    else if (child.getNodeName().equals("Severity")) {
		                        if (text != null)
			                          event.setSeverity(Integer.parseInt(text.getNodeValue()));
		                    }
		                    else if (child.getNodeName().equals("Direction")) {
		                        if (text != null)
			                          event.setDirection(text.getNodeValue());
		                    }
		                    else if (child.getNodeName().equals("ReportDate")) {
		                        if (text != null)
			                          event.setReported(new Date(Long.parseLong(text.getNodeValue())));
		                    }
		                    else if (child.getNodeName().equals("UpdateDate")) {
		                        if (text != null)
			                          event.setUpdated(new Date(Long.parseLong(text.getNodeValue())));
		                    }
		                    else if (child.getNodeName().equals("EndDate")) {
		                        if (text != null)
			                          event.setEnding(new Date(Long.parseLong(text.getNodeValue())));
		                    }
		                }
		                events.addElement(event);
	                }
	            }
	            input.close();
	        }
        }
        catch(Exception ex) {
            ex.printStackTrace(System.err);
            coded = false;
        }
        return coded;
    }
    /**
     * Overrides doStartTag() from BodyTagSupport. Developers should not override
     * this method.
     */
    public int doStartTag() {
        return EVAL_BODY_INCLUDE;
    }
    /**
     * Returns the number of traffic events found within the mile radius of the 
     * provided centerpoint.
     * 
     * @return The number of events found.
     */
    public int eventCount() {
        if (events == null) {
            return 0;
        }
        else {
            return events.size();
        }
    }
    /**
     * Returns the traffic event at the given index.
     * 
     * @return A traffic event as a TrafficEventBean
     */
    public TrafficEventBean getEvent(int i) {
        if (events == null) {
            return null;
        }
        else {
            return (TrafficEventBean) events.elementAt(i);
        }
    }
    /**
     * Overrides doEndTag() from BodyTagSupport. Developers should not override
     * this method.
     */
    public int doEndTag() {
        addTag();
        return EVAL_PAGE;
    }
    /**
     * Adds this tag to the parent GoogleMapTag.
     */
    private void addTag() {
        Tag tag = this;
        while (tag.getParent() != null) {
            if (tag.getParent() instanceof GoogleMapTag) {
                ((GoogleMapTag) tag.getParent()).addTraffic_tag(this);
                return;
            }
            tag = tag.getParent();
        }
    }
    /**
     * Returns the map unique id for this traffic event.
     * 
     * @return The id of the traffic event. This value is generated.
     */
    public String getId() {
        return id;
    }
    /**
     * Sets the map unique id for this traffic event.
     * 
     * @param id A unique id.
     */
    public void setId(String id) {
        this.id = id;
    }
    /**
     * Returns the id of the point used as the centerpoint for scanning events.
     * 
     * @return A GoogleMapPointTag id.
     */
    public String getPoint() {
        return point;
    }
    /**
     * Sets the id of the GoogleMapPointTag used as the centerpoint for scanning
     * for events.
     * 
     * @param point The id of the GoogleMapsPointTag used as the centerpoint.
     */
    public void setPoint(String point) {
        this.point = point;
    }
    /**
     * Returns the radius (in miles) from the centerpoint that will be scanned
     * for traffic events.
     * 
     * @return The radius (in miles) as a double.
     */
    public double getRadius() {
        return radius;
    }
    /**
     * Sets the radius (in miles) from the centerpoint that will be scanned for
     * traffic events.
     * 
     * @param radius The radius to scan (in miles)
     */
    public void setRadius(double radius) {
        this.radius = radius;
    }
    /**
     * Returns the minimum severity of traffic events to scan for. This value should
     * be between 1 (least severe) to 5 (most severe).
     * 
     * @return The minimum severity of events to look for.
     */
    public int getSeverity() {
        return severity;
    }
    /**
     * Sets the type of events to look for. Valid values are 'incident' for non-construction
     * related delays like accidents, 'construction' for construction related events or
     * 'both' for both construction and non-construction related events.
     * 
     * @param type The type of events to look for.
     */
    public void setType(String type) {
        this.type = type;
    }
    /**
     * Retrns the type of events that will be scanned for. Valid values are 'incident' for non-construction
     * related delays like accidents, 'construction' for construction related events or
     * 'both' for both construction and non-construction related events.
     * 
     * @return The type of events being looked for.
     */
    public String getType() {
        return type;
    }
    /**
     * Sets the minimum severity levels of events to look for. This number must be between
     * 1 (least severe) and 5 (most severe).
     * 
     * @return The current minimum severity level of events to look for.
     */
    public void setSeverity(int severity) {
        this.severity = severity;
    }
    /**
     * Sets a GoogleMapIconTag for this group of events, overriding the standard Google Maps marker icon.
     * 
     * @param icon An initialized GoogleMapIconTag
     */
    public void setIcon(GoogleMapIconTag icon) {
        this.icon = icon;
    }
    /**
     * Returns the GoogleMapIconTag that renders this marker, or null if the default
     * markers are used.
     * 
     * @return A GoogleMapIconTag or null.
     */
    public GoogleMapIconTag getIcon() {
        return icon;
    }
}
